package com.fw4j.jdbc.datasource.pool;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.Executor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fw4j.jdbc.datasource.entity.Configure;

public class ConnectionPool{
	private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class);
	private volatile Queue<Fw4jConnection> pool = new LinkedList<Fw4jConnection>();
	private Configure conf = null;
	private int MIN = 5;
	private int MAX = 30;

	private class Fw4jConnection implements Connection{
		private Connection conn = null;
		private long outOfData = -1;
		private int use = 0;
		
		public Fw4jConnection(Connection conn){
			this.conn = conn;
		}
		public Fw4jConnection(Connection conn,long outOfData){
			this.conn = conn;
			this.outOfData = outOfData;
		}
		public void close(){
			if(this.use < 666){
				this.close(false);
				return;
			}
			this.close(true);
			//关闭一个旧连接重新放入一个新连接
			pool.offer(getInstance());
		}
		public void close(boolean close){
			if(close){
				try {
					this.conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}else{
				logger.debug(this.toString()+" offer");
				pool.offer(this);
			}
		}
		@Override
		public boolean isWrapperFor(Class<?> arg0) throws SQLException {
			return this.conn.isWrapperFor(arg0);
		}
		@Override
		public <T> T unwrap(Class<T> arg0) throws SQLException {
			return this.conn.unwrap(arg0);
		}
		@Override
		public void abort(Executor arg0) throws SQLException {
			this.conn.abort(arg0);
		}
		@Override
		public void clearWarnings() throws SQLException {
			this.conn.clearWarnings();
		}
		@Override
		public void commit() throws SQLException {
			this.conn.commit();
		}
		@Override
		public Array createArrayOf(String arg0, Object[] arg1)
				throws SQLException {
			return this.conn.createArrayOf(arg0, arg1);
		}
		@Override
		public Blob createBlob() throws SQLException {
			return this.conn.createBlob();
		}
		@Override
		public Clob createClob() throws SQLException {
			return this.conn.createClob();
		}
		@Override
		public NClob createNClob() throws SQLException {
			return this.conn.createNClob();
		}
		@Override
		public SQLXML createSQLXML() throws SQLException {
			return this.conn.createSQLXML();
		}
		@Override
		public Statement createStatement() throws SQLException {
			return this.conn.createStatement();
		}
		@Override
		public Statement createStatement(int arg0, int arg1)
				throws SQLException {
			return this.conn.createStatement(arg0,arg1);
		}
		@Override
		public Statement createStatement(int arg0, int arg1, int arg2)
				throws SQLException {
			return this.conn.createStatement(arg0, arg1,arg2);
		}
		@Override
		public Struct createStruct(String arg0, Object[] arg1)
				throws SQLException {
			return this.conn.createStruct(arg0, arg1);
		}
		@Override
		public boolean getAutoCommit() throws SQLException {
			return this.conn.getAutoCommit();
		}
		@Override
		public String getCatalog() throws SQLException {
			return this.conn.getCatalog();
		}
		@Override
		public Properties getClientInfo() throws SQLException {
			return this.conn.getClientInfo();
		}
		@Override
		public String getClientInfo(String arg0) throws SQLException {
			return this.conn.getClientInfo(arg0);
		}
		@Override
		public int getHoldability() throws SQLException {
			return this.conn.getHoldability();
		}
		@Override
		public DatabaseMetaData getMetaData() throws SQLException {
			return this.conn.getMetaData();
		}
		@Override
		public int getNetworkTimeout() throws SQLException {
			return this.conn.getNetworkTimeout();
		}
		@Override
		public String getSchema() throws SQLException {
			return this.conn.getSchema();
		}
		@Override
		public int getTransactionIsolation() throws SQLException {
			return this.conn.getTransactionIsolation();
		}
		@Override
		public Map<String, Class<?>> getTypeMap() throws SQLException {
			return this.conn.getTypeMap();
		}
		@Override
		public SQLWarning getWarnings() throws SQLException {
			return this.conn.getWarnings();
		}
		@Override
		public boolean isClosed() throws SQLException {
			return this.conn.isClosed();
		}
		@Override
		public boolean isReadOnly() throws SQLException {
			return this.conn.isReadOnly();
		}
		@Override
		public boolean isValid(int arg0) throws SQLException {
			return this.conn.isValid(arg0);
		}
		@Override
		public String nativeSQL(String arg0) throws SQLException {
			return this.conn.nativeSQL(arg0);
		}
		@Override
		public CallableStatement prepareCall(String arg0) throws SQLException {
			return this.conn.prepareCall(arg0);
		}
		@Override
		public CallableStatement prepareCall(String arg0, int arg1, int arg2)
				throws SQLException {
			return this.conn.prepareCall(arg0, arg1, arg2);
		}
		@Override
		public CallableStatement prepareCall(String arg0, int arg1, int arg2,
				int arg3) throws SQLException {
			return this.conn.prepareCall(arg0, arg1, arg2, arg3);
		}
		@Override
		public PreparedStatement prepareStatement(String arg0)
				throws SQLException {
			return this.conn.prepareStatement(arg0);
		}
		@Override
		public PreparedStatement prepareStatement(String arg0, int arg1)
				throws SQLException {
			return this.conn.prepareStatement(arg0,arg1);
		}
		@Override
		public PreparedStatement prepareStatement(String arg0, int[] arg1)
				throws SQLException {
			return this.conn.prepareStatement(arg0,arg1);
		}
		@Override
		public PreparedStatement prepareStatement(String arg0, String[] arg1)
				throws SQLException {
			return this.conn.prepareStatement(arg0,arg1);
		}
		@Override
		public PreparedStatement prepareStatement(String arg0, int arg1,
				int arg2) throws SQLException {
			return this.conn.prepareStatement(arg0,arg1,arg2);
		}
		@Override
		public PreparedStatement prepareStatement(String arg0, int arg1,
				int arg2, int arg3) throws SQLException {
			return this.conn.prepareStatement(arg0,arg1,arg2);
		}
		@Override
		public void releaseSavepoint(Savepoint arg0) throws SQLException {
			this.conn.releaseSavepoint(arg0);
		}
		@Override
		public void rollback() throws SQLException {
			this.conn.rollback();
		}
		@Override
		public void rollback(Savepoint arg0) throws SQLException {
			this.conn.rollback(arg0);
		}
		@Override
		public void setAutoCommit(boolean arg0) throws SQLException {
			this.conn.setAutoCommit(arg0);
		}
		@Override
		public void setCatalog(String arg0) throws SQLException {
			this.conn.setCatalog(arg0);
		}
		@Override
		public void setClientInfo(Properties arg0)
				throws SQLClientInfoException {
			this.conn.setClientInfo(arg0);
		}
		@Override
		public void setClientInfo(String arg0, String arg1)
				throws SQLClientInfoException {
			this.conn.setClientInfo(arg0, arg1);
		}
		@Override
		public void setHoldability(int arg0) throws SQLException {
			this.conn.setHoldability(arg0);
		}
		@Override
		public void setNetworkTimeout(Executor arg0, int arg1)
				throws SQLException {
			this.conn.setNetworkTimeout(arg0, arg1);
		}
		@Override
		public void setReadOnly(boolean arg0) throws SQLException {
			this.conn.setReadOnly(arg0);
		}
		@Override
		public Savepoint setSavepoint() throws SQLException {
			return this.conn.setSavepoint();
		}
		@Override
		public Savepoint setSavepoint(String arg0) throws SQLException {
			return this.conn.setSavepoint(arg0);
		}
		@Override
		public void setSchema(String arg0) throws SQLException {
			this.conn.setSchema(arg0);
		}
		@Override
		public void setTransactionIsolation(int arg0) throws SQLException {
			this.conn.setTransactionIsolation(arg0);
		}
		@Override
		public void setTypeMap(Map<String, Class<?>> arg0) throws SQLException {
			this.conn.setTypeMap(arg0);
		}
		public long getOutOfData() {
			return outOfData;
		}
		public void setOutOfData(long outOfData) {
			this.outOfData = outOfData;
		}
		public int getUse() {
			return use;
		}
		public void incUse() {
			use++;
		}
	}
	
	private class Daemon extends Thread{
		public Daemon(){
			//创建一个用于检测数据连接是否失效的数据库表
			Connection conn = getInstance();
			try {
				conn.createStatement().execute("create table fw4j_jdbc_connect_status(id int(1));");
				conn.close();
			} catch (SQLException e) {
			}
		}
		public void run(){
			while(true){
				try {
					Thread.sleep(conf.getWatchTime());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				int poolSize = pool.size();
				logger.debug("start Daemon pool size:"+poolSize);
				if(poolSize < MIN){
					for(int i = 0 ;i < MIN ; i++){
						pool.offer(getInstance());
					}
				}
				//判断连接是否失效
				for(int i=0;i<poolSize;i++){
					Fw4jConnection conn = pool.poll();
					logger.debug("Connection use : " +conn.getUse());
					try {
						if((conf.getOutOfData() != -1 && conn.getOutOfData() + conf.getOutOfData() == System.currentTimeMillis()/1000)
								|| conn.isClosed()){
							throw new SQLException();
						}
						conn.createStatement().execute("select * from fw4j_jdbc_connect_status");
						pool.offer(conn);
					} catch (SQLException e) {
						//数据连接失效后重新申请一个新的数据连接
						conn.close(true);
						pool.offer(getInstance());
					}
				}
				logger.debug("end Daemon");
			}
		}
	}
	public ConnectionPool(Configure conf){
		this.conf = conf;	
		this.MIN = conf.getMIN();
		this.MAX = conf.getMAX();
		int poolSize = pool.size();
		if(poolSize < MIN){
			for(int i = 0 ;i < MIN ; i++){
				pool.offer(getInstance());
			}
		}
		Daemon dm = new Daemon();
		dm.setDaemon(true);
		Thread td = new Thread(dm);
		td.start();
	}
	
	private synchronized Fw4jConnection getInstance(){
		try {
			Connection conn = null;
			do{
				logger.info("database:"+this.conf.getUrl());
				Class.forName(this.conf.getDriver());
				conn = DriverManager.getConnection(this.conf.getUrl(),this.conf.getUser(),this.conf.getPwd());
			}while(conn == null);
			return new Fw4jConnection(conn,System.currentTimeMillis()/1000);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	public synchronized Connection getConnection(){
		if(conf.isPool()){
			Fw4jConnection c = pool.poll();
			logger.debug("Connection use : " +c.getUse());
			c.incUse();
			return c;
		}
		return getInstance();
	}	
}
